using System;
using System.Collections.Generic;
using System.Linq;
using MathematicallySafe.HeadCounter.Data.Common;
using MathematicallySafe.HeadCounter.Data;
using MathematicallySafe.HeadCounter.Data.Interfaces;
using MathematicallySafe.HeadCounter.Data.Models;
using StructureMap;

namespace MathematicallySafe.HeadCounter.Services.Calculators
{
    public class LootMetricCalculator : PeriodicCalculation
    {
        private static readonly IOptions Options = ObjectFactory.GetInstance<IOptions>();
        private static readonly IGuild Guild = ObjectFactory.GetInstance<IGuild>();

        private struct LootMetric
        {
            // todo this should be an int and not reused in this lazy way as a mean.
            public double LootCount { get; set; }
            public double LootValue { get; set; }
            public double LootRate { get; set; }
            public double WeightedLootRate { get; set; }
        }

        private int _raiderCount;
        private int _shortPeriodLooterCount;
        private int _longPeriodLooterCount;
        private int _lifetimeLooterCount;

        private LootMetric _shortMean;
        private LootMetric _longMean;
        private LootMetric _lifetimeMean;

        public LootMetricCalculator(int shortPeriod, int longPeriod) : base(shortPeriod, longPeriod) {}

        public double ShortPeriodLootCountMean { get { return _shortMean.LootCount / _raiderCount; } }
        public double ShortPeriodLootValueMean { get { return _shortMean.LootValue / _raiderCount; } }
        public double ShortPeriodLootRateMean { get { return _shortMean.LootRate / _raiderCount; } }
        public double ShortPeriodWeightedLootRateMean { get { return _shortMean.WeightedLootRate / _raiderCount; } }
        public double? ShortPeriodLootPriorityMean { get; private set; }

        public double LongPeriodLootCountMean { get { return _longMean.LootCount /= _raiderCount; } }
        public double LongPeriodLootValueMean { get { return _longMean.LootValue /= _raiderCount; } }
        public double LongPeriodLootRateMean { get { return _longMean.LootRate /= _raiderCount; } }
        public double LongPeriodWeightedLootRateMean { get { return _longMean.WeightedLootRate /= _raiderCount; } }
        public double? LongPeriodLootPriorityMean { get; private set; }

        public double LifetimeLootCountMean { get { return _lifetimeMean.LootCount /= _raiderCount; } }
        public double LifetimeLootValueMean { get { return _lifetimeMean.LootValue /= _raiderCount; } }
        public double LifetimeLootRateMean { get { return _lifetimeMean.LootRate /= _raiderCount; } }
        public double LifetimeWeightedLootRateMean { get { return _lifetimeMean.WeightedLootRate /= _raiderCount; } }
        public double? LifetimeLootPriorityMean { get; private set; }

        protected override void Initialise()
        {
            base.Initialise();

            _raiderCount = 0;
            _shortPeriodLooterCount = 0;
            _longPeriodLooterCount = 0;

            _shortMean.LootCount = 0;
            _shortMean.LootRate = 0;
            _shortMean.LootValue = 0;
            _shortMean.WeightedLootRate = 0;

            _longMean.LootCount = 0;
            _longMean.LootRate = 0;
            _longMean.LootValue = 0;
            _longMean.WeightedLootRate = 0;

            _lifetimeMean.LootCount = 0;
            _lifetimeMean.LootRate = 0;
            _lifetimeMean.LootValue = 0;
            _lifetimeMean.WeightedLootRate = 0;
        }

        protected override void Process(Raider raider)
        {
            _raiderCount++;

            ShortPeriodLootPriorityMean = 0.0;
            LongPeriodLootPriorityMean = 0.0;
            LifetimeLootPriorityMean = 0.0;

            CalculateShortLootMetric(raider);
            CalculateLongLootMetric(raider);
            CalculateLifetimeLootMetric(raider);
        }

        protected override void PostProcess()
        {
            // todo do the other mean calcs like this (and get rid of that that ugly int/double thing on loot count).
            ShortPeriodLootPriorityMean /= _shortPeriodLooterCount;
            LongPeriodLootPriorityMean /= _longPeriodLooterCount;
            LifetimeLootPriorityMean /= _lifetimeLooterCount;

            var shorts = (from r in Guild.ActiveRaiders
                          where r.ShortPeriodLootPriority.HasValue
                          orderby r.ShortPeriodLootPriority
                          select r.ShortPeriodLootPriority).ToArray();

            ShortPeriodLootPriorityMean = shorts[shorts.Count()/2];

            var longs = (from r in Guild.ActiveRaiders
                         where r.LongPeriodLootPriority.HasValue
                         orderby r.LongPeriodLootPriority
                         select r.LongPeriodLootPriority).ToArray();

            LongPeriodLootPriorityMean = longs[longs.Count() / 2];

            var lifetimes = (from r in Guild.ActiveRaiders
                             where r.LifetimeLootPriority.HasValue
                             orderby r.LifetimeLootPriority
                             select r.LifetimeLootPriority).ToArray();

            LifetimeLootPriorityMean = lifetimes[lifetimes.Count() / 2];
        }

        private void CalculateShortLootMetric(Raider raider)
        {
            var lootMetric = CalculateLootMetrics(raider, ShortRaidingPeriod);

            raider.ShortPeriodLootValue = lootMetric.LootValue;
            raider.ShortPeriodLootRate = lootMetric.LootRate;
            raider.ShortPeriodLootCount = (int) lootMetric.LootCount;
            raider.ShortPeriodWeightedLootRate = lootMetric.WeightedLootRate;

            if (lootMetric.LootValue != 0)
            {
                raider.ShortPeriodLootPriority = lootMetric.LootValue / raider.ShortPeriodEffectiveAttendance;

                _shortPeriodLooterCount++;
                ShortPeriodLootPriorityMean += raider.ShortPeriodLootPriority;
            }
            else
            {
                raider.ShortPeriodLootPriority = null;
            }

            _shortMean.LootCount += raider.ShortPeriodLootCount;
            _shortMean.LootRate += raider.ShortPeriodLootRate;
            _shortMean.LootValue += raider.ShortPeriodLootValue;
            _shortMean.WeightedLootRate += raider.ShortPeriodWeightedLootRate;
        }

        private void CalculateLongLootMetric(Raider raider)
        {
            var lootMetric = CalculateLootMetrics(raider, LongRaidingPeriod);

            raider.LongPeriodLootValue = lootMetric.LootValue;
            raider.LongPeriodLootRate = lootMetric.LootRate;
            raider.LongPeriodLootCount = (int)lootMetric.LootCount;
            raider.LongPeriodWeightedLootRate = lootMetric.WeightedLootRate;

            if (lootMetric.LootValue != 0)
            {
                raider.LongPeriodLootPriority = lootMetric.LootValue / raider.LongPeriodEffectiveAttendance;

                _longPeriodLooterCount++;
                LongPeriodLootPriorityMean += raider.LongPeriodLootPriority;
            }
            else
            {
                raider.LongPeriodLootPriority = null;
            }

            _longMean.LootCount += raider.LongPeriodLootCount;
            _longMean.LootRate += raider.LongPeriodLootRate;
            _longMean.LootValue += raider.LongPeriodLootValue;
            _longMean.WeightedLootRate += raider.LongPeriodWeightedLootRate;
        }

        private void CalculateLifetimeLootMetric(Raider raider)
        {
            var lootMetric = CalculateLootMetrics(raider, LifetimeRaidingPeriod);

            raider.LifetimeLootValue = lootMetric.LootValue;
            raider.LifetimeLootRate = lootMetric.LootRate;
            raider.LifetimeLootCount = (int)lootMetric.LootCount;
            raider.LifetimeWeightedLootRate = lootMetric.WeightedLootRate;

            if (lootMetric.LootValue != 0)
            {
                raider.LifetimeLootPriority = lootMetric.LootValue / raider.LifetimeEffectiveAttendance;

                _lifetimeLooterCount++;
                LifetimeLootPriorityMean += raider.LifetimeLootPriority;
            }
            else
            {
                raider.LifetimeLootPriority = null;
            }

            _lifetimeMean.LootCount += raider.LifetimeLootCount;
            _lifetimeMean.LootRate += raider.LifetimeLootRate;
            _lifetimeMean.LootValue += raider.LifetimeLootValue;
            _lifetimeMean.WeightedLootRate += raider.LifetimeWeightedLootRate;
        }

        private LootMetric CalculateLootMetrics(Raider raider, IEnumerable<Raid> raidingPeriod)
        {
            var metric = new LootMetric();
            
            var raids = GetAdjustedRaidingPeriod(raider, raidingPeriod, true);
            var raidCount = raids.Where(r => r.RaiderAttendedAnyChar(raider.MainName)).Count();

            var itemsWon = from r in raids
                           from l in r.Loot
                           where l.Item != null && l.WinnerIdentity == raider.Identity && l.Item.iLevel > Options.iLevelBase
                           select l.Item;

            metric.LootCount = itemsWon.Count();

            var weightedValue = 0.0;
            itemsWon.ForEach(i => weightedValue += i.LootValue(raider));

            metric.LootValue = weightedValue;
            metric.LootRate = itemsWon.Count() / (double) raidCount;
            metric.WeightedLootRate = weightedValue / raidCount;

            return metric;
        }

    }
}